package com.pigcloud.sop.gateway.filter;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.pigcloud.common.core.common.Result;
import com.pigcloud.common.core.constant.GlobalConstant;
import com.pigcloud.common.core.enums.ResponseCodeEnum;
import com.pigcloud.common.core.exception.BizException;
import com.pigcloud.common.core.validator.AssertWrapper;
import com.pigcloud.sop.gatewaycommon.bean.SopConstants;
import com.pigcloud.sop.gateway.config.IgnoreAuthConfig;
import com.pigcloud.sop.gateway.config.JwtTokenConfig;
import com.pigcloud.sop.gatewaycommon.gateway.constant.ContextConstants;
import com.pigcloud.sop.gatewaycommon.gateway.filter.Orders;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;

/**
 * 授权过滤
 *
 * @author zsx
 * @date 2023/12/1 10:30
 */
@Slf4j
@Order(Orders.PRE_ROUTE_PERMISSION_FILTER_ORDER)
@Configuration
public class AuthFilter implements GlobalFilter {


    @Resource
    private IgnoreAuthConfig ignoreAuthConfig;
    @Resource
    private JwtTokenConfig jwtTokenConfig;

    private static final String BEARER = "Bearer ";

    private static final String Basic = "Basic ";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (exchange.getAttribute(SopConstants.RESTFUL_REQUEST) != null) {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            ServerHttpRequest.Builder mutate = request.mutate();
            //跳过预请求
            if (this.methodMatch(request)) {
                return chain.filter(exchange);
            }
            //是否需要忽略token验证
            if (isIgnoreToken(request.getPath().toString())) {
                log.debug("当前接口：{}, 不解析用户token", request.getPath());
                return chain.filter(exchange);
            }
            try {
                //验证token是否存在
                Pair<Boolean, String> verifyRe = verifyToken(exchange, chain);
                if (!verifyRe.getKey()) {
                    return errorConvert(response, ResponseCodeEnum.TOKEN_INVALID.getMessage(), ResponseCodeEnum.TOKEN_INVALID.getCode(), HttpStatus.UNAUTHORIZED);
                }
                //验证token
                parseToken(exchange, mutate);
            } catch (BizException e) {
                return errorConvert(response, e.getMessage(), e.getCode(), HttpStatus.UNAUTHORIZED);
            } catch (Exception e) {
                return errorConvert(response, "验证token出错", ResponseCodeEnum.TOKEN_INVALID.getCode(), HttpStatus.BAD_REQUEST);
            }
            ServerHttpRequest build = mutate.build();
            return chain.filter(exchange.mutate().request(build).build());
        }
        return chain.filter(exchange);
    }

    /**
     * 验证token是否有效
     *
     * @param exchange exchange
     * @param chain    chain
     * @return boolean
     */
    private Pair<Boolean, String> verifyToken(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = getHeader(GlobalConstant.AUTHORIZATION, request);
        boolean re = StringUtils.hasText(token) && (token.startsWith(BEARER) || token.startsWith(Basic));
        return new Pair<>(re, token);
    }


    /**
     * 解析token
     *
     * @param exchange exchange
     */
    private void parseToken(ServerWebExchange exchange, ServerHttpRequest.Builder mutate) throws NoSuchAlgorithmException, InvalidKeySpecException {
        ServerHttpRequest request = exchange.getRequest();
        String token = getHeader(GlobalConstant.AUTHORIZATION, request);
        AssertWrapper.hasText(token, "token不能为空");
        mutate.header(GlobalConstant.AUTHORIZATION, token);
        if (!token.startsWith(BEARER)) {
            return;
        }
        // 解析用户信息
        Claims claims = Jwts.parserBuilder().setSigningKey(jwtTokenConfig.createRsaPublicKey())
                .build().parseClaimsJws(token.substring(BEARER.length())).getBody();

        mutate.header(GlobalConstant.PLATFORM_USER_ID, String.valueOf(claims.get(ContextConstants.PLATFORM_USER_ID)));

        mutate.header(GlobalConstant.PLATFORM_USER_NAME, URLUtil.encode(String.valueOf(claims.get(ContextConstants.PLATFORM_USER_NAME))));
        mutate.header(GlobalConstant.PLATFORM_LOGIN_NAME, URLUtil.encode(String.valueOf(claims.get(ContextConstants.PLATFORM_LOGIN_NAME))));
        mutate.header(GlobalConstant.PLATFORM_USER_PHONE, URLUtil.encode(String.valueOf(claims.get(ContextConstants.PLATFORM_USER_PHONE))));

        mutate.header(GlobalConstant.X_TENANT_ID, claims.get(ContextConstants.TENANT_ID) == null ? null : String.valueOf(claims.get(ContextConstants.TENANT_ID)));
        mutate.header(GlobalConstant.TENANT_USER_ID, claims.get(ContextConstants.TENANT_USER_ID) == null ? null : String.valueOf(claims.get(ContextConstants.TENANT_USER_ID)));
        mutate.header(GlobalConstant.TENANT_USER_NAME, claims.get(ContextConstants.TENANT_USER_NAME) == null ? null : URLUtil.encode(String.valueOf(claims.get(ContextConstants.TENANT_USER_NAME))));
        mutate.header(GlobalConstant.LOGIN_ACCOUNT_SYSTEM, claims.get(ContextConstants.LOGIN_ACCOUNT_SYSTEM) == null ? null : URLUtil.encode(String.valueOf(claims.get(ContextConstants.LOGIN_ACCOUNT_SYSTEM))));
    }


    /**
     * 忽略token
     *
     * @param path path
     * @return boolean
     */
    private boolean isIgnoreToken(String path) {
        return ignoreAuthConfig.isIgnoreToken(path);
    }

    /**
     * 获取请求头信息
     *
     * @param headerName 头名称
     * @param request    request
     * @return java.lang.String
     */
    protected String getHeader(String headerName, ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        if (headers.isEmpty()) {
            return StrUtil.EMPTY;
        }
        String value = headers.getFirst(headerName);
        if (StringUtils.hasText(value)) {
            return value;
        }
        return request.getQueryParams().getFirst(headerName);
    }


    /**
     * 添加请求头
     *
     * @param mutate mutate
     * @param name   头名称
     * @param value  值
     */
    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = URLUtil.encode(valueStr);
        mutate.header(name, valueEncode);
    }


    /**
     * 错误转换
     *
     * @param response   response
     * @param errMsg     errMsg
     * @param errCode    errCode
     * @param httpStatus httpStatus
     * @return reactor.core.publisher.Mono<java.lang.Void>
     */
    protected Mono<Void> errorConvert(ServerHttpResponse response, String errMsg, int errCode, HttpStatus httpStatus) {
        Result<Object> error = Result.error(new BizException(errCode, errMsg));
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        response.setStatusCode(httpStatus);
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONString(error).getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }

    /**
     * 方法匹配
     *
     * @param request http请求对象
     * @return boolean
     */
    private boolean methodMatch(ServerHttpRequest request) {
        return methodMatch(request, HttpMethod.OPTIONS.name(), HttpMethod.TRACE.name());
    }


    /**
     * 方法匹配
     *
     * @param request http请求对象
     * @param methods http请求方式
     * @return boolean
     */
    private boolean methodMatch(ServerHttpRequest request, String... methods) {
        return Arrays.asList(methods).contains(request.getMethod());
    }
}
